package com.vincent.leader;

import com.vincent.common.Environment;
import com.vincent.common.Message;
import com.vincent.common.Process;
import com.vincent.message.AdoptedMessage;
import com.vincent.message.P1aMessage;
import com.vincent.message.P1bMessage;
import com.vincent.message.PreemptedMessage;
import com.vincent.utils.BallotNumber;
import com.vincent.utils.PValueSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 *
 * Scout运行在协议的第1阶段
 *
 * Created by wensen on 2016/12/11.
 */
public class Scout extends Process{

    private static Logger LOGGER = LoggerFactory.getLogger(Scout.class);

    private final int leader;
    private List<Integer> acceptors;
    private BallotNumber ballot_number;

    public Scout(Environment env, int id,
                 int leader, List<Integer> acceptors, BallotNumber ballot) {
        super(env, id);
        this.leader = leader;
        acceptors = new ArrayList<Integer>();
        acceptors.addAll(acceptors);
        ballot_number = new BallotNumber(ballot.round(), ballot.leader_id());

    }

    /**
     * scout发送p1a信息给所有的acceptor并等待p1b信息。这里有两种情况
     *
     * 1、scout记录下所有acceptor接受(accept)的p1b消息，当接收到过半acceptor发送的accept消息，
     *      它学习到该ballot已经被采用了，在这种情况下，将通知他的leader并退出
     * 2、如果scout接收到一个不同的ballot号，表明acceptor接受了一个更高ballot号，这时，scout将
     *      通知它的leader有更高ballot号被采用，然后退出
     */
    public void body() {

        List<Integer> waitfor = new LinkedList<Integer>();
        for (Integer a : acceptors) {
            this.sendMessage(a, new P1aMessage(this.id(), this.ballot_number));
            waitfor.add(a);
        }

        PValueSet pvalues = new PValueSet();
        while (true) {
            Message msg = null;
            try {
                msg = this.getNextMessage();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (msg instanceof P1bMessage) {
                if (this.ballot_number == ((P1bMessage) msg).ballot_number() &&
                        waitfor.contains(msg.src())) {
                    pvalues.update(((P1bMessage) msg).accepted());
                    waitfor.remove(msg.src());
                    if (waitfor.size() < acceptors.size()/2) {
                        this.sendMessage(this.leader, new AdoptedMessage(this.id(),
                                this.ballot_number, pvalues));
                        return;
                    }
                } else {
                    LOGGER.debug("Scout: a bigger ballot number: ", ((P1bMessage) msg).ballot_number());

                    this.sendMessage(this.leader, new PreemptedMessage(this.id(),
                            ((P1bMessage) msg).ballot_number()));
                }
            } else {
                LOGGER.debug("Scout: unexpected msg: {}", msg);
            }

        }

    }
}
